/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package gfdclient;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import repository.exception.FileSizeException;
import repository.exception.NonDirPathException;
import repository.exception.RepositorySizeException;
import repository.ByteArray;
import userservercontroller.IRepositoryClient;
import userservercontroller.WrongKeyExeption;

/**
 * Данный класс представляет собой дерево имитирующее основные особенности репозитория
 * @author Volk ID = 1001
 */
public class JRepositoryTree extends JTree{
  //--------------------Константы класса---------------------
  private final String ROOTNAME = "Репозиторий";
  private final String TEMPDIRECTORY = "TempDirectory";
  //--------------------Аттрибуты класса---------------------
  private IRepositoryClient repository; //Интерфейс репозитория
  private long autorizationKey = 0; //Авторизационный ключ
  //--------------------Сервисные методы--------------------

  /**
   * Получить корневой элемент
   * @return корневой элемент
   */
  private DefaultMutableTreeNode getRoot(){
     DefaultTreeModel model = (DefaultTreeModel) getModel();
     return (DefaultMutableTreeNode) model.getRoot();
  }

  /**
   * Получить папку которая в данный момент активна, если не чего не выбрано возвращается указатель на
   * корневую директорию
   * @return текущая папка
   */
  private DefaultMutableTreeNode getCurrentFolder(){
   //Определяем директорию в которую будет организована вставка
   //Принцип: есть выделенная папка - то вставляем в неё ;
   //         есть выделенный файл - вставленный в отцовскую папку
   //         ничего не выделено - вставляем в корень

   DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) getLastSelectedPathComponent();
   //Если выделений нет, то считаем, что вставку нужно производить в корневую директорию
   DefaultMutableTreeNode currentFolder;
   if(selectedNode == null) currentFolder = getRoot();
    else if(!selectedNode.getAllowsChildren())
    currentFolder = (DefaultMutableTreeNode) selectedNode.getParent();
    else currentFolder = selectedNode;
   return currentFolder;
  }

  /**
   * Сгенерировать путь
   * @param objectName - имя папки или файла, путь до которого необходимо сгенерировать
   * @param currentFolder - текущая папка
   * @return путь до файла относительно репозитория пользователя
   */
  private String generatePath(String objectName, DefaultMutableTreeNode currentFolder){
   if(objectName == null) objectName = "";
   //Если папка корень, то возвращаем имя такущей папки
   if(currentFolder.isRoot()) return currentFolder.toString() + File.separator + objectName;
   //Формируем путь до текущей папки
   String path = "";
   path = currentFolder.toString();
   while(!currentFolder.isRoot()){
    currentFolder = (DefaultMutableTreeNode) currentFolder.getParent();
    path = currentFolder.toString() + File.separator + path;
   }
   path += File.separator + objectName; 
   return path;
  }

  private boolean deleteTreeNode(String path) throws RemoteException, WrongKeyExeption{
   StringTokenizer tokenizer = new StringTokenizer(path, File.separator);
   path = ""; 
   String  delNodeName = "";

   //Разделение пути к вершине от имени вершины
   if(tokenizer.countTokens() > 1) path += tokenizer.nextToken();
    for(int i = 0; i < tokenizer.countTokens(); i++)
     path += File.separator + tokenizer.nextToken();
    if(tokenizer.hasMoreTokens()) delNodeName = tokenizer.nextToken();


   //Получение объекта вершины выступающей в роли отцовской директории
   DefaultMutableTreeNode node = PathToNode(path);
  
   //Если node == null, значит были удалены все отцовские папки, нужно продолжать рекрсиный процесс
   String RepPath = transformTreePathToRepPath(path);
   //String RepPath = path;

   if(!repository.exist(autorizationKey, RepPath)) return deleteTreeNode(path);
    else{ //Если отцовская вершина существует, то удаляем из неё вершину с заданным именем
     for(int i = 0; i < node.getChildCount(); i++){
      if(node.getChildAt(i).toString().equals(delNodeName)){
        node.remove(i);
        return true;
      }
     }
    }

   return false;
  }

  /**
   * Получить по текущей директории, объект в дереве, в случае если объекта не существует вернуть null-значение
   * @param path путь до вершину относительно репозитория пользователя
   * @return ссылку на искомую вершину или null-значение если вершина не найдена
   */
  private DefaultMutableTreeNode PathToNode(String path){
   //Необходимо организовать разбор выражения и поиск необходимой вершины
   DefaultMutableTreeNode result = getRoot();
   if(path == null) return result;
    else result = null;
   StringTokenizer tokenizer = new StringTokenizer(path, File.separator);
   String currentName;
   while(tokenizer.hasMoreTokens()){
    currentName = tokenizer.nextToken();
    result = findNode(currentName, result);
    if(result == null) return null;
   }

   return result;
  }

  /**
   * Найти вершину в текущей директории по имени
   * @param name - имя искомой вершины
   * @param rootNode - ссылка на вершину рассматриваемую как текущую директорию
   * @return ссылку на искомую вершину или null-значение если вершина не найдена
   */
  private DefaultMutableTreeNode findNode(String name, DefaultMutableTreeNode rootNode){
    if(rootNode == null) return getRoot(); 
    for(int i = 0; i < rootNode.getChildCount(); i++){
      DefaultMutableTreeNode node = (DefaultMutableTreeNode) rootNode.getChildAt(i);
      String curName = (String)node.getUserObject();
      if(curName.equals(name)) return node;
    }
    return null;
  }

  /**
   * Получить путь выделенного объекта
   * @return путь к выделённому объекту, null - в случае если не одного объекта не выделено
   */
  private String generateSelectObjectPath(){
   DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) getLastSelectedPathComponent();
   if(selectedNode == null) return null;
   DefaultMutableTreeNode currentFolder = getCurrentFolder();
   String objectName = null;
   if(!currentFolder.toString().equals(selectedNode.toString())) objectName = selectedNode.toString();
   String path = generatePath(objectName, currentFolder);
   return path;
  }
  
 /**
   * Получить путь выделенного объекта относительно репозитория
   * @return путь к выделённому объекту, null - в случае если не одного объекта не выделено
   */
  public String getSelectedObjectPath(){
	  return transformTreePathToRepPath(generateSelectObjectPath());
  }
 /**
  * Преобразовать путь в дереве в путь в репозитории
  * @param TreePath путь в дереве
  * @return путь в репозитории
  */
  private String transformTreePathToRepPath(String TreePath){
     int maxlen = ROOTNAME.length();
     TreePath = TreePath.substring(maxlen+1);
     return TreePath;
  }

  /**
   * Считать содержимое файла в массив байт
   * @param f файл
   * @return массив байт, если файл не существует или является директорией - null
   * @throws java.io.IOException ошибка чтения
   */
  private ByteArray getByteArray(File f) throws IOException{
        //проверить на директорию
        if(f.isDirectory()) return null;
        //создать массив байт
        ByteArray array=new ByteArray((int)f.length());
        try {
            //создать файловый поток
            FileInputStream in = new FileInputStream(f);
            try{
                //побайтно считать файл
                int ch;
                while((ch=in.read())!=-1) array.write((byte) ch);
                array.chopCounter(); //сбросить счетчик
            }finally{in.close();} //закрыть поток
        } catch (FileNotFoundException ex) {  return null;}
        return array; //вернуть массив байт
  }

  /**
   * Записать содержимое массива байтов в файл
   * @param path путь до файла
   * @param array массив байт
   * @return файл, если файл не создан - null
   * @throws java.io.IOException ошибка записи
   */
  private File createFile(String path, ByteArray array)
            throws IOException {
        //проверить на null
        if (array==null) array=new ByteArray(0);
        //создать файл
        File f=new File(path);
        try {
            //создать файловый поток
            FileOutputStream out = new FileOutputStream(f);
            try{
                //записать файл
                out.write(array.getAll());
            }finally{out.close();} //закрыть поток
        } catch (FileNotFoundException ex) {  return null;}
        return f;
  }

  //--------------------Интерфейсные методы-----------------

  /**
   * Конструктор по умолчанию. После вызова конструктор дерево всё ещё остаётся не инициализированным,
   * поэтому необходимо вызвать метод init.
   */
  public JRepositoryTree(){
   //Добавляем в дерево корневой узел, который не отображается
   DefaultMutableTreeNode root = new DefaultMutableTreeNode(ROOTNAME);
   DefaultTreeModel model = new DefaultTreeModel(root);
   setModel(model);
  }

  /**
   * Инициализация дерева
   * @param repository - ссылка на интерфейс репозитория
   * @param autorizationKey - авторизационный ключ
   */
  public void init(IRepositoryClient repository, long autorizationKey) throws RemoteException, WrongKeyExeption{
   if(repository == null) throw new NullPointerException();
   this.repository = repository;
   this.autorizationKey = autorizationKey;

   //Здесь должно происходить получение всех файлов репозитория и занесения их в дерево
   updateFileList();
   //----------------------------------------------------------------------------------
  }

  /**
   * Создать папку, в выделенную папку или в корень если не одна папка не выделина
   * @return Путь относительно репзитория - папка успешно создана, null - папка с таким именем уже существует
   * @throws java.rmi.RemoteException - соединение потеряно, необходимо переподключение
   * @throws java.io.IOException - ошибка доступа к жёсткому диску
  */
  public String createFolder() throws RemoteException, IOException, WrongKeyExeption{
   String folderName = "Новая папка";
   //Получить текущую папку
   DefaultMutableTreeNode insertFolder = getCurrentFolder(); 
   //Формируем путь, по которому организуется вставка в репозитории 
   String path = transformTreePathToRepPath(generatePath(folderName, insertFolder));

   //Если создание папки в репозитории завершилось успехом, то добавляем папку в дерево, иначе 
   //возвращает false, что означает, что такая папка в заданной директории уже существует
        if (repository.createDirectory(autorizationKey, path)) {
            DefaultMutableTreeNode resultNode = new DefaultMutableTreeNode(folderName);
            resultNode.setAllowsChildren(true);
            insertFolder.add(resultNode);
            updateUI();
            return path;
        }
   return null;
  }

  /**
   * Импортировать файл
   * @param file - импортируемый файл, в выделенную папку или в корень если не одна папка не выделина
   * @return Путь относительно репозитория - файл успешно создан, null - необходимо указать на директорию в дереве
   * @throws java.rmi.RemoteException - связь прервана, необходимо переподключение
   * @throws java.io.IOException - ошибка доступа к жёсткому диску
   * @throws repository.exception.FileSizeException - превышен допустимый размер файла
   * @throws repository.exception.RepositorySizeException - превышен допустимый размер репозитория
   */
  public String importFile(File file) throws RemoteException, IOException, FileSizeException, RepositorySizeException, WrongKeyExeption{
   if(file == null) throw new NullPointerException();
   String path = generateSelectObjectPath();
   if(path == null) return null;
   String realpath = transformTreePathToRepPath(path);
   if(!repository.isDirectory(autorizationKey, realpath)) return null;
   
   DefaultMutableTreeNode importDirectory = PathToNode(path);
   if(importDirectory == null) return null;
   
   ByteArray buffer = getByteArray(file); 
   realpath = realpath + file.separator + file.getName();
   
   if(repository.uploadFile(autorizationKey, realpath, buffer)){
     DefaultMutableTreeNode importObject = new DefaultMutableTreeNode(file.getName());
     importObject.setAllowsChildren(false);
     importDirectory.add(importObject);
     updateUI();
     return realpath;
   }

   return null;
  }

  /**
   * Удалить выделенный файл
   * @return Путь относительно репозитория в случае успеха, null - в случае еслине выделен объект для удалоения в дереве
   * @throws java.rmi.RemoteException - связь прервана, необходимо переподключение
   */
  public String delete() throws RemoteException, WrongKeyExeption{
   String path = generateSelectObjectPath();
   if(path == null) return null;
   DefaultMutableTreeNode deleteObject = PathToNode(path);
   if(deleteObject == null) return null;
   if(deleteObject.isRoot()) return null;

   String realpath = transformTreePathToRepPath(path); 
   if(repository.deleteFile(autorizationKey, realpath)){
      DefaultMutableTreeNode parent = (DefaultMutableTreeNode) deleteObject.getParent();  
      deleteObject.removeAllChildren();
       //Поиск и удаление объекта в дереве------
      for(int i = 0; i < parent.getChildCount(); i++){
        DefaultMutableTreeNode temp = (DefaultMutableTreeNode) parent.getChildAt(i);
        if(temp.toString().equals(deleteObject.toString())){
          parent.remove(i);
          break; 
        }
      }

    //---------------------------------------
    updateUI(); 
    return realpath;
   }
   return null;
  }

/**
 * Получить выделенный файл
 * @return файл в случае успеха, null - в случае если файл не выбран
 * @throws java.rmi.RemoteException - разрыв соединения
 * @throws userservercontroller.WrongKeyExeption - авторизационный ключ потерял актуальность
 * @throws java.io.IOException - ошибка чтения файла или транспортировки по сети
 */
  public ByteArray getFile() throws RemoteException, WrongKeyExeption, IOException{
   String path = generateSelectObjectPath();
   if(path == null) return null;
   String realpath = transformTreePathToRepPath(path);
   if(repository.isDirectory(autorizationKey, realpath)) return null;

   DefaultMutableTreeNode readDirectory = PathToNode(path);
   if(readDirectory == null) return null;

   ByteArray array = repository.downloadFile(autorizationKey, realpath);
   if(array != null){
    //-----------Вот тут надо преобразовать массив байт в объект класса файд---
    // realpath = TEMPDIRECTORY + File.separator + realpath;
    //File resultFile = createFile(realpath, array);
    return array;
    //-------------------------------------------------------------------------
   } 

   return null;
  }

  /**
   * Обновить авторизационный ключ
   * @param autorizationKey - новй авторизационный ключ
   */
  public void updateAutorizationKey(long autorizationKey){
    this.autorizationKey = autorizationKey;  
  }

  private void updateFileList(String path) throws RemoteException, WrongKeyExeption{

      //Получение объекта вершины выступающая в роли  текущей директории
   DefaultMutableTreeNode node = PathToNode(path);

   String treePath = path;
   // Если верщина не является корневой, то преобразуем путь удаляя фиктивную папку репозиторий
   if(!node.isRoot()) path = transformTreePathToRepPath(path);
    else path = "";

   //Если node равна null, то вершина была удалена некоторым процессом на сервере
   if(!repository.exist(autorizationKey, path)){
    updateFileList(ROOTNAME + File.separator);
    return;
   }
   //------------------------------------------------------------------------------------------

   //Если обновляемая вершина - файл, то заканчиваем обработку
   if(!node.getAllowsChildren()) return;
   //Очистка содержимого-----------------
   node.removeAllChildren();
   //------------------------------------
   //Получение списка файлов находящихся в текущей директории
   Vector<String> rootFiles = repository.getFiles(autorizationKey, path);
   //Организация прохода по элементам текущей директории
   Iterator<String> iter = rootFiles.iterator();
   while(iter.hasNext()){
    String curObject = iter.next();
    //Если мы имеем папку то добавляем её в список и вызываем метод рекурсивного спуска
    DefaultMutableTreeNode temp = new DefaultMutableTreeNode(curObject);
    String newpath;
    if(path != null) newpath = path + File.separator + curObject;
     else newpath = curObject;
    node.add(temp);
    if(repository.isDirectory(autorizationKey, newpath)){
     temp.setAllowsChildren(true);
     //path = temp.getUserObject().toString();
     updateFileList(autorizationKey, temp, newpath);
     newpath = null;
    }else{ //В противном случае вопринимаем результаты как файлы и просто добавляем их в список
     temp.setAllowsChildren(false);
    }
   }
   updateUI();

  }



  /**
   * Обновить список файлов
   */
  public void updateFileList() throws RemoteException, WrongKeyExeption{
   //Получение текущего пути от которого необходимо начинать обновление
   String path = generateSelectObjectPath();
   updateFileList(path);
  }

  private void updateFileList(long autorizationKey, DefaultMutableTreeNode node, String path) throws RemoteException, WrongKeyExeption{
  //Получение списка файлов находящихся в текущей директории
    Vector<String> rootFiles = repository.getFiles(autorizationKey, path);
  //Организация прохода по элементам текущей директории
    Iterator<String> iter = rootFiles.iterator();
   while(iter.hasNext()){
    String curObject = iter.next();
    //Если мы имеем папку то добавляем её в список и вызываем метод рекурсивного спуска
    DefaultMutableTreeNode temp = new DefaultMutableTreeNode(curObject);
    node.add(temp);
    String newpath = path + File.separator + curObject;
    if(repository.isDirectory(autorizationKey, newpath)){
     temp.setAllowsChildren(true);
     updateFileList(autorizationKey, temp, newpath);
    }else{ //В противном случае вопринимаем результаты как файлы и просто добавляем их в список
     temp.setAllowsChildren(false);
    }
   }
  }

  /**
   * Удалить все записи
   */
 private void clear(){
   DefaultMutableTreeNode root = getRoot();
   root.removeAllChildren();
 }
  //---------------------------------------------------------
}
